(Rails) ダウングレードする7.0.3.1 -> 6.1.6.1

 Railsでダウングレードする。

状況

諸事情によりrailsのバージョンを7.0.3.1から6.1.6.1に変更する必要があった。(6.1.4を入れたかったが、6.1.4を指定しても7.0.3.1が入ってしまった。)

各アプリケーションに移動する。

cdコマンドで各アプリケーションに移動してください。

Railsのバージョンを確認する。

$rails -v #railsのversionを確認するコマンド。
rails 7.0.3.1

Rails,Railtiesをアンインストールする

$gem uninstall rails  #railsをuninstallするコマンド。
Successfully uninstalled rails-7.0.3.1
$gem uninstall railties #railtiesをuninstallするコマンド。
Successfully uninstalled railties-7.0.3.1
#----#
#この時にほかのralisのバージョンが入っていると、以下のようになる。
Select gem to uninstall:
 1. rails-6.1.4
 2. rails-6.1.6.1
 3. rails-7.0.3.1
 4. All versions
> 3 #4でもOK 
#uninstallするバージョンを選んでくださいということ。

Rails 6.1.6.1を入れなおす

gem install rails -v 6.1.6.1 ##railsをinstallするコマンド

Gemfileの更新

今の状態ではGemfileに7.0.3.1を入れるように記述があるのでそこを変更する。

Gemfile

gem 'rails', '~> 7.0.3', '>= 7.0.3.1' #変更前
gem 'rails', '~> 6.1.6', '>= 6.1.6.1' #変更後

bundleを更新する。

$bundle update #ここでエラーがでると思われる。
Bundler could not find compatible versions for gem "actionpack":
In snapshot (Gemfile.lock): actionpack (>= 7.0.3.1)
In Gemfile: rails (~> 6.1.4) was resolved to 6.1.4,
which depends on actionpack (= 6.1.4) importmap-rails was resolved to 1.1.5,
which depends on railties (>= 6.0.0) was resolved to 7.0.3.1,
which depends on actionpack (= 7.0.3.1) 
Running `bundle update` will rebuild your snapshot from scratch,
using only the gems in your Gemfile, which may resolve the conflict.

actionpackが7.0.3.1と6.1.4どちらの方で対応すればよいのかがわかっていない状態。なので、一つ一つ変えてあげる。

$bundle update --conservative actionpack #またエラーが出た。
Bundler could not find compatible versions for gem "activesupport":
.
.
.

同じような感じで今回はactivesupportなので追記する。

$bundle update --conservative actionpack activesupport #エラーがなくなるまで繰り返す

 bundle update ができたらバージョンを確認する。

$rails -v
rails 6.1.6.1

これで入ったので完了。

参考文献

bundle update --conservative で狙った gem だけをアップデートする - ユユユユユ

Rails バージョンアップ時の bundle update におけるバージョンコンフリクト解消方法について

(Rails) エラーを読んでみよう

rails のエラーについて

railsで何かプログラムを作っていると、様々なエラーに直面すると思います。 そこでエラーの意味と怪しい場所について説明します。

今回説明する怪しい部分、対策法は一例ですので、必ずにもその部分を直した結果エラーが改善するとは限りません。ご了承ください。

  1. NoMethodError
  2. NameError
  3. Routing Error
  4. ArgumentError

NoMethodError

これは、method(機能)がうまく使えないときにでるエラーです。

undefined method `title' for nil:NilClass

意味:nil:NilClass の未定義のメソッド `title'(Google翻訳より)

意訳:titleというものは*nilには対応していない

*nil:空白クラスと呼ばれるもの,未定義の時にこのクラスに分類される,何も入っていないと思っても大丈夫

対策:titleが間違っているか、titleの前の@bookが未定義となっていると思われるため、Controllerにて定義がされていないと思われるので、Controllerでしっかりと、@bookが定義されているかを確認する。

詳細:@bookの情報がtitleに流れるが、その際@bookには何も入っていないため、titleは「何もない情報」を受け取ることになる。この時、titleは何もない情報を受け取れない(対応していない)のでエラーが発生する。

NameError

定義されていない変数(variable)やメソッド(method)が使われている。

undefined local variable or method `back' for #<ActionView::Base:0x007f8cbcb56878>

意味:#<ActionView::Base:0x007f8cbcb56878> の未定義のローカル変数またはメソッド「back」(Google翻訳より)

意訳:backというものを私(rails)は知らないよー

対策:backというものを定義するetc...

詳細:今回は、link_toでbackという文字そのものを表示させたかったため、"back"にして、<%=link_to "back", books_path%>という形にした。

Routing Error

urlで検索したページがroutesに記述されていない

No route matches [GET] "/index"

意味:[GET] "/index" に一致するルートはありません(Google翻訳より)

意訳:urlで検索したページがroutesに記述されていない

対策: urlのスペルミス,routesへの書き込み不足を疑う。

詳細:/indexではなく、/booksだったので、urlの部分を/indexに直した。

ArgumentError

0個渡して、2個受け取ろうとしているので、数がおかしい。

wrong number of arguments (given 0, expected 2)

意味:引数の数が間違っています (指定された 0、予想される 2)(Google翻訳より)

意訳:受け取り口が2つあって、0個渡しているからちゃんと2個渡してね

対策: def test(width,height)の部分を削除する,testを呼び出している部分に(100,100)等で2個渡して挙げるようにする。

詳細:今回は@book.testの方に(100,10)と追加させた。

まとめ

このように基本的にエラーに関しては英語で書いてあり、慣れていくとエラーの意味が分かるので、エラーが出た際に一度読む癖をつけるのがおすすめです。

(c言語)2次元配列を作る(malloc対応)

状況

2次元配列の仕組みの振り返り

素数nに対してmallocをどのようにしたらできるのかの備忘録

1次元配列

void main(){
  int i;
  int a[2]={1,2};
  for(i=0;i<2;i++){
    printf("a[%d]=%d\n",i,a[i]); 
  }  
}
a[0]=1
a[1]=2

2次元配列

int i,j;
  int a[2][3]={{1,2,3},{4,5,6}};//2×3の配列
  for(i=0;i<2;i++){//行
    for(j=0;j<3;j++){//列
      printf("a[%d][%d]=%d\n",i,j,a[i][j]); 
    }
  }
a[0][0]=1
a[0][1]=2
a[0][2]=3
a[1][0]=4
a[1][1]=5
a[1][2]=6

少し表示を変えると,

1,2,3
4,5,6

つまり、書く時の書き方を変えてみる。

  int a[3][3]={{1,2,3}
              ,{4,5,6}};

少し見やすくなったかな?、、

ここまででわかったこと

a[i][j] iは行を示すjは列を示す。

mallocによる任意の2次元配列

やり方は2通り 1. 1回のmallocですべての領域を確保する 2. 2回mallocする

イメージは以下のとおりである。

それではコードを見てみよう。

void main(){
  int *a;
  int i,j;
  //やり方1//
  printf("--method[1]--\n");
  a=(int*)malloc(sizeof(int)*(2*2));//2*2の配列を作る
  for(i=0;i<4;i++){
    a[i]=i;
  }
  for(i=0;i<4;i++){
    printf("%d",a[i]);
    if(i==2-1){
      printf("\n");//見やすいように
    }    
  }
  printf("\n");
free(a);
}
  //やり方2//
void main(){
  printf("--method[2]--\n");
  int **aa;
  aa=(int**)malloc(sizeof(int)*2);//行2の配列
  for(i=0;i<2;i++){
    aa[i]=(int*)malloc(sizeof(int)*3);//列3の配列
    
  }
  for(i=0;i<2;i++){//初期化(配列に0を代入)
    for(j=0;j<3;j++){
      aa[i][j]=i+j;
    }
  }
  for(i=0;i<2;i++){//行
    for(j=0;j<3;j++){//列
      printf("%d",aa[i][j]);//出力
    }
    if(j!=3-1){//見やすいように
      printf("\n");
    }
  }
free(aa);
}

結果は以下のとおりである。

--method[1]--
01
23
--method[2]--
012
123

まとめ

  • しっかりと行列を考えてあげることで計算ができる。
  • n次元行列に対しては、2のやり方だと、最初にaaをポインタのポインタのポインタの...にする必要があるので不向き
  • よって、1のやり方で、0番目を0のx方向、1番目を0のy方向,2番目を0のz方向...のように組み合わせるのがベストである気がする。

(MIPS) 初学者向け SIPMで領域を扱う

内容

データセグメントにてメモリ(領域)を確保する際に、その扱い方をします。 (つまり、グローバル変数でメモリ(領域)を定義します。)

メモリに対して使う主な命令(32bit機とする,64bit機なら1Word=8byte)

命令 使用例 意味
la la $rt,即値($rs) $rtに$rsのアドレスを入れる
lw lw $rt,即値($rs) $rtに$rsの1Word(4byte)分を入れる
sw sw $rt,即値($rs) $rsから1Word(4byte)分を$rtを入れる
lb lb $rt,即値($rs) $rtに$rsの1byte分を入れる
sb sb $rt,即値($rs) $rsから1byte分を$rtを入れる

補足

  • laは$rt=&rsと同等である。

  • char*s[10]ならchar型は1byteであるから、lb,sbを多用する

  • int*s[10]ならint型は4byteであるから、lw,swを多用する

こんな感じである

intにおけるメモリの使い方。

取得、変更、表示を行う。

まずはc言語で記述

c言語

int s[4];
void print(int n) { //print関数
  int i = 0;
  for (i = 0; i < 4; i++) {
    printf("%d %d\n", &s[i], s[i]); //アドレスと値を表示
  }
}

void main() {
  s[0] = 0;
  s[1] = 0;
  s[2] = 0;
  s[3] = 0;
  print(4); //全て0
  s[0] = 1;
  s[1] = 2;
  s[2] = 3;
  s[3] = 4;
  print(4); //1,2,3,4を順に表示
}

結果(c言語)

#アドレス #値
862081056 0
862081060 0 #アドレスが4ずつ増えている
862081064 0
862081068 0
862081056 1 #アドレスが一番上と同じ
862081060 2
862081064 3
862081068 4

アセンブリ言語

  .data
  s:        .space 16       #領域確保
  sp:      .asciiz " "     #スペース
  nl:      .asciiz "\n"    #改行
  .text
  .globl main
main:
  add $sp, $sp, -4    # 退避場所の確保
  sw  $ra, 0($sp)     # 退避

  la  $a0, s          # $a0 <- sのポインタ
  sw  $zero, 0($a0)   # $a0 <- 0
  sw  $zero, 4($a0)   # $a0 <- 0
  sw  $zero, 8($a0)   # $a0 <- 0
  sw  $zero, 12($a0)  # $a0 <- 0

  li $a1,0            #ループ用のindex
  li $a2,16           #ループ用の限界値(ループ回数)
  jal print           #print

  la $a0,s            # $a0 <- sのポインタ
  li $t0,1            # $t0 = 1
  sw  $t0, 0($a0)     # $a0 <- 1
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++4
  sw  $t0, 0($a0)     # $a0 <- 2
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++ 4
  sw  $t0, 0($a0)     # $a0 <- 3
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++ 4
  sw  $t0, 0($a0)     # $a0 <- 4

  li $a1,0            #ループ用のindex
  li $a2,16           #ループ用の限界値(ループ回数)
  jal print           #print

  lw  $ra, 0($sp)     #戻す
  add $sp, $sp, 4     #退避場所からの移動
  jr $ra

print:
  la $a0,s            # $t0 <- sの領域アドレス
  add $a0,$a0,$a1     # sのアドレス+α
  move $t0, $a0       # $t0に$a0の保存
  la $a0,0($a0)       # $a0 <- $t0のアドレス
  li $v0, 1           # syscall用
  syscall             # 整数値($a0)の表示
  la $a0, sp          # スペース用
  li $v0, 4           # syscall用
  syscall             # スペースの表示
  lw $a0,0($t0)       # $a0 <- $t0のアドレスの値
  li $v0, 1           # syscall 用
  syscall             # 整数値($a0)の表示
  la $a0, nl          # 改行用
  li $v0, 4           # syscall 用
  syscall             # 改行の表示
  add $a1,$a1,4       # ループ用のインデックス
  bne $a1,$a2 print   # ループ
  jr $ra

結果(アセンブリ言語)

#アドレス #値
268500992 0
268500996 0 #アドレスが4ずつ増えている
268501000 0
268501004 0
268500992 1 #アドレスが一番上と同じ
268500996 2
268501000 3
268501004 4

注意点

  • laとlwをしっかりと使い分ける
  • 4byte分をしっかりと加算する
  • アドレスがc言語アセンブリ言語共に4ずつ増えている。
  • lwやswの即値の部分にレジスタの値(変数)は使えないので、即値($rs)の$rsの部分をループごとに加算していくようにする
  • $a0は一つしかなく、syscallで毎回使うので、アドレスの位置をずらすときは$a0に直接加算するのではなく、レジスタを使う。もし直接$a0に加算するなら、改行等のsyscallの時に退避させる。

charにおけるメモリの使い方。

取得、変更、表示を行う。

まずはc言語で記述

c言語

#include <stdio.h>
char s[] = "abcd";

void print(int n) {  // print関数
  int i = 0;
  for (i = 0; i < 4; i++) {
    printf("%d %s\n", &s[i], (s + i));  //アドレスと値を表示
  }
}

void main() {
  print(4);
  // abcdと表示
  s[0] = 'A';
  s[1] = 'B';
  s[2] = 'C';
  s[3] = 'D';
  print(4);  // ABCDと表示
}

結果(c言語)

#アドレス #値
1104089104 abcd
1104089105 bcd #アドレスが1ずつ増えている
1104089106 cd
1104089107 d
1104089104 ABCD #アドレスが一番上と同じ
1104089105 BCD
1104089106 CD
1104089107 D

アセンブリ言語

  .data
  before:      .asciiz "abcde"
  .asciiz "A"
  .asciiz "B"
  .asciiz "C"
  .asciiz "D"
  sp:      .asciiz " "     #スペース
  nl:      .asciiz "\n"    #改行

  .text
  .globl main
main:
  add $sp, $sp, -4    # 退避場所の確保
  sw  $ra, 0($sp)     # 退避

  la  $a0, before     # $a0 <- beforeのポインタ

  li $a1,0            #ループ用のindex
  li $a2,4            #ループ用の限界値(ループ回数)
  jal print           #print

  la $a0,before       # $a0 <- beforeのポインタ
  lb $t0,after_1      # $a0 <- after_1のポインタ
  sw  $t0, 0($a0)     # $a0 <- "A"
  addi $t0, $t0, 1    # "A"の次の"B"を指す
  addi $a0, $a0, 1    # $a0++1
  sb  $t0, 0($a0)     # $a0 <- "B"
  addi $t0, $t0, 1    # "B"の次の"C"を指す
  addi $a0, $a0, 1    # $a0++ 1
  sb  $t0, 0($a0)     # $a0 <- "C"
  addi $t0, $t0, 1    # "C"の次の"D"を指す
  addi $a0, $a0, 1    # $a0++ 1
  sb  $t0, 0($a0)     # $a0 <- "D"

  li $a1,0            #ループ用のindex
  li $a2,4           #ループ用の限界値(ループ回数)
  jal print           #print

  lw  $ra, 0($sp)     #戻す
  add $sp, $sp, 4     #退避場所からの移動
  jr $ra

print:
  la $a0,before       # $t0 <- beforeの領域アドレス
  add $a0,$a0,$a1     # beforeのアドレス+α
  move $t0, $a0       # $t0に$a0の保存
  la $a0,0($a0)       # $a0 <- $t0のアドレス
  li $v0, 1           # syscall用
  syscall             # 整数値($a0)の表示
  la $a0, sp          # スペース用
  li $v0, 4           # syscall用
  syscall             # スペースの表示
  move $a0,$t0       # $a0 <- $t0のアドレスの値
  syscall             # 整数値($a0)の表示
  la $a0, nl          # 改行用
  syscall             # 改行の表示
  add $a1,$a1,1       # ループ用のインデックス
  bne $a1,$a2 print   # ループ
  jr $ra

結果(アセンブリ言語)

#アドレス #値
268500992 abcd
268500993 bcd #アドレスが4ずつ増えている
268500994 cd
268500995 d
268500992 ABCD アドレスが一番上同じ
268500993 BCD
268500994 CD
268500995 D

注意点

  • lalbをしっかりと使い分ける
  • 1byte分をしっかりと加算する
  • アドレスがc言語アセンブリ言語共に1ずつ増えている。
  • データセグメントはつながっている。(名前を指定しないでafter_1のみをロードして、後はそのアドレスに1を加算していたがうまくいっている)

まとめ

  • la,lw,sw,sb,lbをしっかりと使い分ける必要がある。
  • intは4byte,charは1byteずつ移動していることに注目する

(MIPS)アセンブリ言語でポインタを操る

 状況

アセンブリ言語をやっていて、領域に対しての操作が複数あり、特にループ文の中でポインタを操る方法をまとめました。

言葉で説明すると、データセグメントで、領域を確保し、その後、テキストセグメントで命令を実行します。

    .data
s:  .space 20       #領域確保
nl: .asciiz "\n"    #改行
    .text
    .globl main
main:
  add $sp, $sp, -4  # 退避場所の確保
  sw  $ra, 0($sp)   # 退避
  la  $a0, s        # $a0 <- sのポインタ
  sw  $zero, 0($a0) # $a0 <- 0
  addi $a0, $a0, 4  # $a0<- $a0 + 4
  sw  $zero, 0($a0) # $a0 <- 0
  li $a1,0          #5回printを繰り返す
  li $a2,20          #5回printを繰り返す
  jal print         #print
  lw  $ra, 0($sp)   #戻す
  add $sp, $sp, 4   #退避場所からの移動
  jr $ra
print:
  la $t0,s          # $t0 <- sの領域アドレス
  add $t0,$t0,$a1   # sのアドレス+α
  lw $a0,0($t0)     # $a0 <- $t0のアドレスの値
  li $v0, 1         # syscall 用
  syscall           # 整数値($a0)の表示
  la $a0, nl        # 改行用
  li $v0, 4         # syscall 用
  syscall           # 改行の表示
  add $a1,$a1,4     # ループ用のインデックス
  bne $a1,$a2 print # ループ
  jr $ra

このとき、領域のポインタを変更する方法としていくつかの方法が挙げられる。(領域がint型(4バイト)であるとする。)

  1. 毎回pの指す場所を変える。
  2. 1の改良版(即値の利用)
  3. *(p+i)を使い、代入時に位置を指定する

1 毎回pの指す場所を変える。

実行前にp=p+1のようなコードを書くことにより実現する。

int型は4バイトであるので、(p+1)はp+4を意味する必要がある。

c言語

int *a=&s;  //aに領域のアドレスを入れる
*a=0;       //領域の0番目 <- 0
a=a+1;      //(1は自動的に4として扱われる)
*a=0;       //領域の4番目 <- 0

アセンブリ言語

la $a0, s           # $a0 <- sのポインタ
sw $zero ,0($a0)    #領域の0番目 <- 0
addi $a0,$a0,4      #領域の先頭から4番目を指す
sw $zero ,0($a0)    #領域の4番目 <- 0

2. 1からaddiを削除する(即値の利用)

c言語

int *a=&s;  //aに領域のアドレスを入れる
*a=0;       //領域の0番目 <- 0
*(a+1)=0;   //!!変更点!!

アセンブリ言語

la $a0, s           # $a0 <- sのポインタ
sw $zero ,0($a0)    #領域の0番目 <- 0
sw $zero ,4($a0)    #領域の4番目 <- 0

このやり方は、値を入れる回数が整数値(即値)でわかるときのみ有効であり、関数を作成したときの実用性的には若干低めである。

3. *(p+i)を使い、代入時に位置を指定する

その前に一度変数を使ったやり方

c言語

int *a=&s; //aに領域のアドレスを入れる
i=0;       //初期化
*a=0;      //領域の0番目 <- 0
i=i+4;     //i=4
a=a+i;     //領域の先頭から4番目を指す
*a=0;      //領域の4番目 <- 0
a=a-i;     //領域の先頭から0番目を指す
i=i+4;     //i=8
a=a+i;     //領域の先頭から8番目を指す
*a=0;      //領域の8番目 <- 0
a=a-i;     //領域の先頭から0番目を指す

アセンブリ言語

la $a0, s       # $a0 <- 領域のポインタ
li $t0,0        # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,4  # $t0 = 4
add $a0,$a0,$t0 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す

subしてaddしてと無駄が多いのではないかと思うかもしれないが、*(p+i)等のポインタに対して、変数を渡して、ポインタの指す位置を変更することを実現するためには必要なことである。 今回、毎回subする形にしているが、これにより、pの値をほかの場所で使う際も気にせずに使うことができる。(独立性の確保)

本題

c言語

int *a=&s;// aに領域のアドレスを入れる
int i=0;  // 初期化
*a=0;     // 領域の0番目 <- 0
i=i+1;    // *(a+i)と書いたときにiの値は強制的に4倍される
*(a+i)=0; // 領域の4番目 <- 0
i=i+1;    // i = 2
*(a+i)=0; // 領域の8番目 <- 0

同じコードを繰り返しているつまり、、

c言語

int *a=&s;      //aに領域のアドレスを入れる
int i=0;        //初期化
*a=0;           //領域の0番目 <- 0
for(i=0;;i++){  //条件式なし
*(a+i)=0;       //領域のi\*4番目 <- 0
}

ここでiの加算値を1にしておくことで条件式等を考えるときに楽である。

これをアセンブリ言語にすると以下のようになる。

アセンブリ言語

la $a0, s       # $a0 <- 領域のポインタ
li $t0,0;       # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,1  # $t0 = 1
mult $t0, 4     # $t0 * 4
mflo $t2        # $t2 = 4
add $a0,$a0,$t2 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t2 # 領域の先頭から0番目を指す
addi $t0,$t0,1  # $t0 = 2
mult $t0, 4     # $t0 * 4
mflo $t2        # $t2 = 8
add $a0,$a0,$t2 # 領域の先頭から8番目を指す
sw $zero ,0($a0)# 領域の8番目 <- 0
sub $a0,$a0,$t2 # 領域の先頭から0番目を指す

indexの値を常に4ずつ上げる

アセンブリ言語

la $a0, p       # $a0 <- 領域のポインタ
li $t0,0        # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,4  # $t0 = 4
add $a0,$a0,$t0 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す
addi $t0,$t0,4  # $t0 = 8
add $a0,$a0,$t0 # 領域の先頭から8番目を指す
sw $zero ,0($a0)# 領域の8番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す

まとめ

  • 領域に対する操作で様々な方法で領域に文字を代入した。
  • 即値を使うやり方は、領域に入れたい値(定数で)と位置が確定しているのであればとても有効である。
  • multを用いたやり方が汎用性は高いと思われる。

(c言語)リングバッファのキューの無駄をなくす

状況

リングバッファのキューは先頭に戻す処理時に1つ無駄ができることを知ったので、改善策を考えた。(enqueueのみ)

リングバッファのキューとは

リングバッファのキューは、キューの限界に領域まで達したら、先頭に戻すことでデキューをタイミングよくすれば無限に入るキューが完成する。

これがリングバッファのキューである。

enqueue以外のコードに関しては以下である。

#include <stdio.h>
#include <stdlib.h>

#define SIZE 4

struct queue {
  int head, tail;
  char elem[SIZE];
};
char dequeue(struct queue *q) {
  char val;
  if (q->head == q->tail) {
    printf("queue underflow\n");
    exit(1);
  } else {
    val = q->elem[q->head];
    q->head++;
    if (q->head >= SIZE) {
      q->head = 0;
    }
    return val;
  }
}

int main(void) {
  struct queue Q;
  char e, moji[5];
  int input, i;

  Q.head = 0;
  Q.tail = 0;
  printf("キューに格納されている値\n");
  for (i = 0; i < 10; i++) {
    printf("%i:::%c\n", i, Q.elem[i]);
  }
  while (1) {
    // メニュー表示
    printf("操作を選択:\n");
    printf("  1: 格納\n");
    printf("  2: 取得\n");
    scanf("%d", &input);  // 操作を入力

    switch (input) {
      case 1:  // 格納
        printf("格納する文字を入力してください:");
        scanf("%s", moji);
        enqueue(&Q, moji[0]);
        printf("\n");
        printf("キューに格納されている値\n");
        for (i = Q.head; i <= Q.tail; i++) {
          printf("%i::%c\n", i, Q.elem[i]);
        }
        printf("tailの値::%d\n", Q.tail);
        printf("headの値::%d\n", Q.head);
        break;
      case 2:  //出力
        e = dequeue(&Q);
        printf("%c\n", e);
        printf("キューに格納されている値\n");
        for (i = Q.head; i <= Q.tail; i++) {
          printf("%i::%c\n", i, Q.elem[i]);
        }
        printf("tailの値::%d\n", Q.tail);
        printf("headの値::%d\n", Q.head);
        break;

        break;
    }
  }
  return 0;
}

問題となる例とコード

以下が今回の問題となる部分です。

  • 先頭に値を入れていない。(*1)
  • または、全てが埋まった瞬間にoverflowとなってしまい、デキューの可能性を否定している。(*2)

*1

void enqueue(struct queue *q,char val){
  q->tail++;
  if(q->tail==SIZE){//最後まで行ったら先頭に戻す
    q->tail=0;
  }
  if(q->tail==q->head){//先頭と最後の指す位置が同じならoverflowとする
    printf("queue overflow\n");
  }else{
     q->elem[q->tail] = val;//値の代入
  }
}

*2

void enqueue(struct queue *q,char val){
  q->elem[q->tail] = val;//値の代入
  q->tail++;
  if(q->tail==SIZE){//最後まで行ったら先頭に戻す
    q->tail=0;
  }
  if(q->tail==q->head){//先頭と最後の指す位置が同じならoverflowとする
    printf("queue overflow\n");
  }
}

解決のためのコード

上記の問題を解決するために以下のようなコードにします。

void enqueue(struct queue *q, char val) {
  if ((q->tail) % SIZE == (q->head) % SIZE && (q->tail) != 0) {
    printf("queue overflow\n");
  } else {
    q->elem[(q->tail) % SIZE] = val;
    q->tail++;
  }
  return;
}

処理内容

  1. tailをSIZEで割った値の余りとheadをSIZEで割った値の余りを比較する
  2. 一緒であり、tailの値が0でないoverflowとする
  3. 違うならtailをSIZEで割った値の余り番目に値を代入する

詳細

  • リングバッファで何回も周回してもSIZEで割った際の余りは常に1周目の場所と同じところを指すため、SIZEで割った余りの値を参考にoverflow等を判定している。
  • また、if文の条件式において、&&の後ろは&&の前半部分のみだけだと、一番最初にenqueueをした際にはじかれてしまうので、最初でないことを示すために書いている。

まとめ

  • 今回、リングバッファのキューにおいて無駄があったため解決策を探した。
  • 正直、余りを使った構造で無駄をなくす構造なのでoverflowしていないのにおかしいと思う人はぜひ活用していただきたい。

(c言語)ポインタを用いたリスト

ココでは主に挿入と削除について話します。(最後に作成したものをチェックするprintも紹介します。)

  • 単方向リスト
  • 双方向リスト

単方向リスト

単方向リストとは

単方向リストは下の図のようなもので、1つのリストが次の行先のみを知っているようなものです。

上の図を以下の図のように変換することで挿入(insert)及び削除(delete)を作る際にイメージしやすくなります。

実際に書いてみましょう

挿入(insert)及び削除(delete)以外の部分は以下のようになります。

struct node{
  struct node *next; //nextのアドレスを入れる部分
  int elem; //値を入れる部分
};
struct node* head(void){
  struct node* n;//箱の生成
  n=(struct node*)malloc(sizeof(struct node));//箱の拡張
  n->next = NULL;//箱の行き先をnullにする
  return n;
}
void main(){
struct node* list;
list=head;
}

ここからがinsert及びdeleteです

void insert(struct node* p, char elem) {
  struct node* n;  //新しく値を代入する箱を作る
  n = (struct node*)malloc(sizeof(struct node));  //箱の大きさを拡張する
  n->elem = elem;                                 //値を代入
  n->next = p->next;  // nの行先をpの行き先にする
  p->next = n;        //作った箱の行先をpにする=次にpが来る
}

void delete(struct node* p){
  if(p->next != NULL){
    p->next=p->next->next;    //pの行き先を次の次にする=一つ飛ばす
  }
}

注意点

insertでは受け取った引数のpの次に値を挿入し、deleteでは受け取った引数pの次の値を削除する形となります。

作成時のポイント

  • 追加する箱を考えて、追加する箱のnext部分を先に指定する。
  • その後で追加した箱の一つ後ろ(引数で渡したもの)のnextを変える。

双方向リスト

双方向リストとは

双方向リストは下の図のようなもので、1つのリストが次の行先を知っていて、また、1つのリストが前の行き先も知っているようなものです。

上の図を以下の図のように変換することで挿入(insert)及び削除(delete)を作る際にイメージしやすくなります。

実際に書いてみましょう

挿入(insert)及び削除(delete)以外の部分は以下のようになります。

struct node{
  struct node *next;  //nextのアドレスを入れる場所
  struct node *prev;  //prevのアドレスを入れる場所
  int elem; //値を入れる場所
};
struct node* head(void){
  struct node *n;//箱の生成
  n=(struct node*)malloc(sizeof(struct node));//箱の拡張
  n->next = NULL;//箱の行き先を指定
  n->prev = NULL;//箱の戻り値を指定(使うことはない)
  return n;
}
void main(){
struct node* list;
list=head;
}

ここからがinsert及びdeleteです

void insert(struct node* p, int elem) {
  struct node* n;  //新しく値を代入する箱を作る
  n = (struct node*)malloc(sizeof(struct node));  //箱の大きさを拡張する
  n->elem = elem;                                 //値を代入

  if (p->next == NULL) {  //一個目の代入のみ
    n->next = p->next;    // nの行き先をpの行き先の次の物にする
    n->prev = p;          // nの戻り先をpにする
    p->next = n;          // pの行き先をnにする
  } else {
    n->next = p->next;  // nの行き先をpの行き先の次の物にする
    n->prev = p;        // nの戻り先をpの戻り先pにする
    p->next->prev = n;  // pの行き先の一つ前をnにする
    p->next = n;        // pの行き先をnにする
  }
}

void delete(struct node* p){
  if(p != NULL){
    p->prev->next=p->next;//pの一つ前(prev)を一つ後(next)を繋げる
    p->next->prev=p->prev;//pの一つ後(next)と一つ前(prev)を繋げる
  }
}

注意点

insertでは受け取った引数のpの次に値を挿入し、deleteでは受け取った引数pを削除する形となります。

作成時のポイント

  • 新しく挿入する値のprevとnextの行き先を先に決める
  • headのみの場合とhead以外がある場合で注意をする
  • 図に書きながら、prevとnextの行き先を線で結び、コードを書いたら消すようにする。

おまけ(作ったリストの全表示)

リストの中身のみ表示(単方向、双方向対応)

void printlist(struct node* p) {  //listを渡す
  p = p->next;    //listの先に行く
  for (; p != NULL; p = p->next) {
    printf("%c ", p->elem); //表示
  }
  printf("\n");
}

リストの詳細を表示(単方向リスト用)

void detailprintf_1(struct node* p) { //listを渡す
  int i = 0;
  p = p->next;    //listの一つ先へ進む
  for (; p != NULL; p = p->next) {
    printf("[%d]elem:%c\n", i, p->elem);  //値の表示
    if (p->next != NULL) {
      printf("[%d]nextelem:%c\n", i, p->next->elem);  //nextの表示
    } else {
      printf("[%d]nextelem:NULL(end)\n", i);  //nextがNULLだったNULL(end)と表示
    }
    i++;      //先頭からの番号を数える
  }
}

リストの詳細を表示(双方向リスト用)

void detailprintf_2(struct node* p) {   //listを渡す
  int i = 0;
  p = p->next;        //listの一つ先に進む
  for (; p != NULL; p = p->next) {
    printf("[%d]elem:%c\n", i, p->elem);  //値の表示
    if (p->next != NULL) {
      printf("[%d]nextelem:%c\n", i, p->next->elem);  //nextの表示
    } else {
      printf("[%d]nextelem:NULL(end)\n", i);  //nextがNULLだったNULL(end)と表示
    }
    if (i != 0) {
      printf("[%d]prevelem:%c\n", i, p->prev->elem);  //prevの表示
    } else {
      printf("[%d]prevelem:HEAD\n", i);   //prevがheadだったらHEADと表示
    }
    i++;      //先頭からの番号を数える
  }
}

結果(双方向リスト)

int main(void) {
  struct node* list;

  list = head();

  insert(list, 'a');
  insert(list, 'b');
  insert(list->next, 'c');
  insert(list, 'd');
  insert(list, 'e');
  delete (list->next);
  printlist(list);
  detailprintf(list);

  return 0;
}
d b c a
[0]elem:d             //先頭(0番目)の値
[0]nextelem:b         //次の値(1番目)
[0]prevelem:HEAD      //前の値(-1番目)
[1]elem:b             //1番目の値
[1]nextelem:c         //次の値(2番目)
[1]prevelem:d         //前の値(0番目)
[2]elem:c             //2番目の値
[2]nextelem:a         //次の値(3番目)
[2]prevelem:b         //前の値(1番目)
[3]elem:a             //3番目の値
[3]nextelem:NULL(end) //次の値(4番目)
[3]prevelem:c         //前の値(2番目)

 まとめ

今回、ポインタを用いたリストの単方向リスト、双方向リストについて書きました。

図を書きながらやるとイメージしやすいです。